From af917c4ade51dff5ffb934c5d6d8f366c4977dd4 Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Mon, 26 Dec 2016 17:11:13 +0100 Subject: [PATCH] vulkan: Handle linear gradients Note: We interpolate premultiplied colors as per the CSS spec. This i different from Cairo, which interpolates unpremultiplied. So in testcases with translucent gradients, it's actually Cairo that is wrong. --- gsk/Makefile.am | 2 + gsk/gskrendernodeimpl.c | 32 +++ gsk/gskrendernodeprivate.h | 5 + gsk/gskvulkanlineargradientpipeline.c | 224 ++++++++++++++++++ gsk/gskvulkanlineargradientpipelineprivate.h | 41 ++++ gsk/gskvulkanrender.c | 6 +- gsk/gskvulkanrenderpass.c | 64 +++++ gsk/gskvulkanrenderprivate.h | 3 + .../vulkan/linear-clip-rounded.frag.glsl | 78 ++++++ .../vulkan/linear-clip-rounded.frag.spv | Bin 0 -> 6748 bytes .../vulkan/linear-clip-rounded.vert.glsl | 84 +++++++ .../vulkan/linear-clip-rounded.vert.spv | Bin 0 -> 6208 bytes gsk/resources/vulkan/linear-clip.frag.glsl | 33 +++ gsk/resources/vulkan/linear-clip.frag.spv | Bin 0 -> 2320 bytes gsk/resources/vulkan/linear-clip.vert.glsl | 78 ++++++ gsk/resources/vulkan/linear-clip.vert.spv | Bin 0 -> 5856 bytes gsk/resources/vulkan/linear.frag.glsl | 33 +++ gsk/resources/vulkan/linear.frag.spv | Bin 0 -> 2320 bytes gsk/resources/vulkan/linear.vert.glsl | 89 +++++++ gsk/resources/vulkan/linear.vert.spv | Bin 0 -> 7648 bytes 20 files changed, 771 insertions(+), 1 deletion(-) create mode 100644 gsk/gskvulkanlineargradientpipeline.c create mode 100644 gsk/gskvulkanlineargradientpipelineprivate.h create mode 100644 gsk/resources/vulkan/linear-clip-rounded.frag.glsl create mode 100644 gsk/resources/vulkan/linear-clip-rounded.frag.spv create mode 100644 gsk/resources/vulkan/linear-clip-rounded.vert.glsl create mode 100644 gsk/resources/vulkan/linear-clip-rounded.vert.spv create mode 100644 gsk/resources/vulkan/linear-clip.frag.glsl create mode 100644 gsk/resources/vulkan/linear-clip.frag.spv create mode 100644 gsk/resources/vulkan/linear-clip.vert.glsl create mode 100644 gsk/resources/vulkan/linear-clip.vert.spv create mode 100644 gsk/resources/vulkan/linear.frag.glsl create mode 100644 gsk/resources/vulkan/linear.frag.spv create mode 100644 gsk/resources/vulkan/linear.vert.glsl create mode 100644 gsk/resources/vulkan/linear.vert.spv diff --git a/gsk/Makefile.am b/gsk/Makefile.am index 31f9eacf21..31dc094eba 100644 --- a/gsk/Makefile.am +++ b/gsk/Makefile.am @@ -29,6 +29,7 @@ gsk_private_vulkan_source_h = \ gskvulkanclipprivate.h \ gskvulkancolorpipelineprivate.h \ gskvulkancommandpoolprivate.h \ + gskvulkanlineargradientpipelineprivate.h \ gskvulkanimageprivate.h \ gskvulkanmemoryprivate.h \ gskvulkanpipelineprivate.h \ @@ -43,6 +44,7 @@ gsk_private_vulkan_source_c = \ gskvulkanclip.c \ gskvulkancolorpipeline.c \ gskvulkancommandpool.c \ + gskvulkanlineargradientpipeline.c \ gskvulkanimage.c \ gskvulkanmemory.c \ gskvulkanpipeline.c \ diff --git a/gsk/gskrendernodeimpl.c b/gsk/gskrendernodeimpl.c index 4e0254625c..cade17a956 100644 --- a/gsk/gskrendernodeimpl.c +++ b/gsk/gskrendernodeimpl.c @@ -383,6 +383,38 @@ gsk_repeating_linear_gradient_node_new (const graphene_rect_t *bounds, return &self->render_node; } +const graphene_point_t * +gsk_linear_gradient_node_peek_start (GskRenderNode *node) +{ + GskLinearGradientNode *self = (GskLinearGradientNode *) node; + + return &self->start; +} + +const graphene_point_t * +gsk_linear_gradient_node_peek_end (GskRenderNode *node) +{ + GskLinearGradientNode *self = (GskLinearGradientNode *) node; + + return &self->end; +} + +const gsize +gsk_linear_gradient_node_get_n_color_stops (GskRenderNode *node) +{ + GskLinearGradientNode *self = (GskLinearGradientNode *) node; + + return self->n_stops; +} + +const GskColorStop * +gsk_linear_gradient_node_peek_color_stops (GskRenderNode *node) +{ + GskLinearGradientNode *self = (GskLinearGradientNode *) node; + + return self->stops; +} + /*** GSK_BORDER_NODE ***/ typedef struct _GskBorderNode GskBorderNode; diff --git a/gsk/gskrendernodeprivate.h b/gsk/gskrendernodeprivate.h index 1139e51da8..729026370e 100644 --- a/gsk/gskrendernodeprivate.h +++ b/gsk/gskrendernodeprivate.h @@ -46,6 +46,11 @@ GskRenderNode * gsk_render_node_deserialize_node (GskRenderNodeType type, GVaria double gsk_opacity_node_get_opacity (GskRenderNode *node); +const graphene_point_t * gsk_linear_gradient_node_peek_start (GskRenderNode *node); +const graphene_point_t * gsk_linear_gradient_node_peek_end (GskRenderNode *node); +const gsize gsk_linear_gradient_node_get_n_color_stops (GskRenderNode *node); +const GskColorStop * gsk_linear_gradient_node_peek_color_stops (GskRenderNode *node); + const GskRoundedRect * gsk_border_node_peek_outline (GskRenderNode *node); float gsk_border_node_get_width (GskRenderNode *node, guint i); const GdkRGBA * gsk_border_node_peek_color (GskRenderNode *node, guint i); diff --git a/gsk/gskvulkanlineargradientpipeline.c b/gsk/gskvulkanlineargradientpipeline.c new file mode 100644 index 0000000000..cc699495fa --- /dev/null +++ b/gsk/gskvulkanlineargradientpipeline.c @@ -0,0 +1,224 @@ +#include "config.h" + +#include "gskvulkanlineargradientpipelineprivate.h" + +struct _GskVulkanLinearGradientPipeline +{ + GObject parent_instance; +}; + +typedef struct _GskVulkanLinearGradientInstance GskVulkanLinearGradientInstance; + +struct _GskVulkanLinearGradientInstance +{ + float rect[4]; + float start[2]; + float end[2]; + gint32 repeating; + gint32 stop_count; + float offsets[GSK_VULKAN_LINEAR_GRADIENT_PIPELINE_MAX_COLOR_STOPS]; + float colors[GSK_VULKAN_LINEAR_GRADIENT_PIPELINE_MAX_COLOR_STOPS][4]; +}; + +G_DEFINE_TYPE (GskVulkanLinearGradientPipeline, gsk_vulkan_linear_gradient_pipeline, GSK_TYPE_VULKAN_PIPELINE) + +static const VkPipelineVertexInputStateCreateInfo * +gsk_vulkan_linear_gradient_pipeline_get_input_state_create_info (GskVulkanPipeline *self) +{ + static const VkVertexInputBindingDescription vertexBindingDescriptions[] = { + { + .binding = 0, + .stride = sizeof (GskVulkanLinearGradientInstance), + .inputRate = VK_VERTEX_INPUT_RATE_INSTANCE + } + }; + static const VkVertexInputAttributeDescription vertexInputAttributeDescription[] = { + { + .location = 0, + .binding = 0, + .format = VK_FORMAT_R32G32B32A32_SFLOAT, + .offset = 0, + }, + { + .location = 1, + .binding = 0, + .format = VK_FORMAT_R32G32_SFLOAT, + .offset = G_STRUCT_OFFSET (GskVulkanLinearGradientInstance, start), + }, + { + .location = 2, + .binding = 0, + .format = VK_FORMAT_R32G32_SFLOAT, + .offset = G_STRUCT_OFFSET (GskVulkanLinearGradientInstance, end), + }, + { + .location = 3, + .binding = 0, + .format = VK_FORMAT_R32_SINT, + .offset = G_STRUCT_OFFSET (GskVulkanLinearGradientInstance, repeating), + }, + { + .location = 4, + .binding = 0, + .format = VK_FORMAT_R32_SINT, + .offset = G_STRUCT_OFFSET (GskVulkanLinearGradientInstance, stop_count), + }, + { + .location = 5, + .binding = 0, + .format = VK_FORMAT_R32G32B32A32_SFLOAT, + .offset = G_STRUCT_OFFSET (GskVulkanLinearGradientInstance, offsets), + }, + { + .location = 6, + .binding = 0, + .format = VK_FORMAT_R32G32B32A32_SFLOAT, + .offset = G_STRUCT_OFFSET (GskVulkanLinearGradientInstance, offsets) + sizeof (float) * 4, + }, + { + .location = 7, + .binding = 0, + .format = VK_FORMAT_R32G32B32A32_SFLOAT, + .offset = G_STRUCT_OFFSET (GskVulkanLinearGradientInstance, colors[0]), + }, + { + .location = 8, + .binding = 0, + .format = VK_FORMAT_R32G32B32A32_SFLOAT, + .offset = G_STRUCT_OFFSET (GskVulkanLinearGradientInstance, colors[1]), + }, + { + .location = 9, + .binding = 0, + .format = VK_FORMAT_R32G32B32A32_SFLOAT, + .offset = G_STRUCT_OFFSET (GskVulkanLinearGradientInstance, colors[2]), + }, + { + .location = 10, + .binding = 0, + .format = VK_FORMAT_R32G32B32A32_SFLOAT, + .offset = G_STRUCT_OFFSET (GskVulkanLinearGradientInstance, colors[3]), + }, + { + .location = 11, + .binding = 0, + .format = VK_FORMAT_R32G32B32A32_SFLOAT, + .offset = G_STRUCT_OFFSET (GskVulkanLinearGradientInstance, colors[4]), + }, + { + .location = 12, + .binding = 0, + .format = VK_FORMAT_R32G32B32A32_SFLOAT, + .offset = G_STRUCT_OFFSET (GskVulkanLinearGradientInstance, colors[5]), + }, + { + .location = 13, + .binding = 0, + .format = VK_FORMAT_R32G32B32A32_SFLOAT, + .offset = G_STRUCT_OFFSET (GskVulkanLinearGradientInstance, colors[6]), + }, + { + .location = 14, + .binding = 0, + .format = VK_FORMAT_R32G32B32A32_SFLOAT, + .offset = G_STRUCT_OFFSET (GskVulkanLinearGradientInstance, colors[7]), + } + }; + static const VkPipelineVertexInputStateCreateInfo info = { + .sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO, + .vertexBindingDescriptionCount = G_N_ELEMENTS (vertexBindingDescriptions), + .pVertexBindingDescriptions = vertexBindingDescriptions, + .vertexAttributeDescriptionCount = G_N_ELEMENTS (vertexInputAttributeDescription), + .pVertexAttributeDescriptions = vertexInputAttributeDescription + }; + + return &info; +} + +static void +gsk_vulkan_linear_gradient_pipeline_finalize (GObject *gobject) +{ + //GskVulkanLinearGradientPipeline *self = GSK_VULKAN_LINEAR_GRADIENT_PIPELINE (gobject); + + G_OBJECT_CLASS (gsk_vulkan_linear_gradient_pipeline_parent_class)->finalize (gobject); +} + +static void +gsk_vulkan_linear_gradient_pipeline_class_init (GskVulkanLinearGradientPipelineClass *klass) +{ + GskVulkanPipelineClass *pipeline_class = GSK_VULKAN_PIPELINE_CLASS (klass); + + G_OBJECT_CLASS (klass)->finalize = gsk_vulkan_linear_gradient_pipeline_finalize; + + pipeline_class->get_input_state_create_info = gsk_vulkan_linear_gradient_pipeline_get_input_state_create_info; +} + +static void +gsk_vulkan_linear_gradient_pipeline_init (GskVulkanLinearGradientPipeline *self) +{ +} + +GskVulkanPipeline * +gsk_vulkan_linear_gradient_pipeline_new (GskVulkanPipelineLayout *layout, + const char *shader_name, + VkRenderPass render_pass) +{ + return gsk_vulkan_pipeline_new (GSK_TYPE_VULKAN_LINEAR_GRADIENT_PIPELINE, layout, shader_name, render_pass); +} + +gsize +gsk_vulkan_linear_gradient_pipeline_count_vertex_data (GskVulkanLinearGradientPipeline *pipeline) +{ + return sizeof (GskVulkanLinearGradientInstance); +} + +void +gsk_vulkan_linear_gradient_pipeline_collect_vertex_data (GskVulkanLinearGradientPipeline *pipeline, + guchar *data, + const graphene_rect_t *rect, + const graphene_point_t *start, + const graphene_point_t *end, + gboolean repeating, + gsize n_stops, + const GskColorStop *stops) +{ + GskVulkanLinearGradientInstance *instance = (GskVulkanLinearGradientInstance *) data; + gsize i; + + if (n_stops > GSK_VULKAN_LINEAR_GRADIENT_PIPELINE_MAX_COLOR_STOPS) + { + g_warning ("Only %u color stops supported.", GSK_VULKAN_LINEAR_GRADIENT_PIPELINE_MAX_COLOR_STOPS); + n_stops = GSK_VULKAN_LINEAR_GRADIENT_PIPELINE_MAX_COLOR_STOPS; + } + instance->rect[0] = rect->origin.x; + instance->rect[1] = rect->origin.y; + instance->rect[2] = rect->size.width; + instance->rect[3] = rect->size.height; + instance->start[0] = start->x; + instance->start[1] = start->y; + instance->end[0] = end->x; + instance->end[1] = end->y; + instance->repeating = repeating; + instance->stop_count = n_stops; + for (i = 0; i < n_stops; i++) + { + instance->offsets[i] = stops[i].offset; + instance->colors[i][0] = stops[i].color.red; + instance->colors[i][1] = stops[i].color.green; + instance->colors[i][2] = stops[i].color.blue; + instance->colors[i][3] = stops[i].color.alpha; + } +} + +gsize +gsk_vulkan_linear_gradient_pipeline_draw (GskVulkanLinearGradientPipeline *pipeline, + VkCommandBuffer command_buffer, + gsize offset, + gsize n_commands) +{ + vkCmdDraw (command_buffer, + 6, n_commands, + 0, offset); + + return n_commands; +} diff --git a/gsk/gskvulkanlineargradientpipelineprivate.h b/gsk/gskvulkanlineargradientpipelineprivate.h new file mode 100644 index 0000000000..50b986e3d5 --- /dev/null +++ b/gsk/gskvulkanlineargradientpipelineprivate.h @@ -0,0 +1,41 @@ +#ifndef __GSK_VULKAN_LINEAR_GRADIENT_PIPELINE_PRIVATE_H__ +#define __GSK_VULKAN_LINEAR_GRADIENT_PIPELINE_PRIVATE_H__ + +#include + +#include "gskvulkanpipelineprivate.h" +#include "gskrendernode.h" + +G_BEGIN_DECLS + +#define GSK_VULKAN_LINEAR_GRADIENT_PIPELINE_MAX_COLOR_STOPS 8 + +typedef struct _GskVulkanLinearGradientPipelineLayout GskVulkanLinearGradientPipelineLayout; + +#define GSK_TYPE_VULKAN_LINEAR_GRADIENT_PIPELINE (gsk_vulkan_linear_gradient_pipeline_get_type ()) + +G_DECLARE_FINAL_TYPE (GskVulkanLinearGradientPipeline, gsk_vulkan_linear_gradient_pipeline, GSK, VULKAN_LINEAR_GRADIENT_PIPELINE, GskVulkanPipeline) + +GskVulkanPipeline * gsk_vulkan_linear_gradient_pipeline_new (GskVulkanPipelineLayout * layout, + const char *shader_name, + VkRenderPass render_pass); + +gsize gsk_vulkan_linear_gradient_pipeline_count_vertex_data + (GskVulkanLinearGradientPipeline*pipeline); +void gsk_vulkan_linear_gradient_pipeline_collect_vertex_data + (GskVulkanLinearGradientPipeline*pipeline, + guchar *data, + const graphene_rect_t *rect, + const graphene_point_t *start, + const graphene_point_t *end, + gboolean repeating, + gsize n_stops, + const GskColorStop *stops); +gsize gsk_vulkan_linear_gradient_pipeline_draw (GskVulkanLinearGradientPipeline*pipeline, + VkCommandBuffer command_buffer, + gsize offset, + gsize n_commands); + +G_END_DECLS + +#endif /* __GSK_VULKAN_LINEAR_GRADIENT_PIPELINE_PRIVATE_H__ */ diff --git a/gsk/gskvulkanrender.c b/gsk/gskvulkanrender.c index 59c592408e..a2968cc11e 100644 --- a/gsk/gskvulkanrender.c +++ b/gsk/gskvulkanrender.c @@ -10,6 +10,7 @@ #include "gskvulkanblendpipelineprivate.h" #include "gskvulkancolorpipelineprivate.h" +#include "gskvulkanlineargradientpipelineprivate.h" #define ORTHO_NEAR_PLANE -10000 #define ORTHO_FAR_PLANE 10000 @@ -316,7 +317,10 @@ gsk_vulkan_render_get_pipeline (GskVulkanRender *self, { "blit", gsk_vulkan_blend_pipeline_new }, { "color", gsk_vulkan_color_pipeline_new }, { "color-clip", gsk_vulkan_color_pipeline_new }, - { "color-clip-rounded", gsk_vulkan_color_pipeline_new } + { "color-clip-rounded", gsk_vulkan_color_pipeline_new }, + { "linear", gsk_vulkan_linear_gradient_pipeline_new }, + { "linear-clip", gsk_vulkan_linear_gradient_pipeline_new }, + { "linear-clip-rounded", gsk_vulkan_linear_gradient_pipeline_new } }; g_return_val_if_fail (type < GSK_VULKAN_N_PIPELINES, NULL); diff --git a/gsk/gskvulkanrenderpass.c b/gsk/gskvulkanrenderpass.c index 0721e9a74a..44bc7f9052 100644 --- a/gsk/gskvulkanrenderpass.c +++ b/gsk/gskvulkanrenderpass.c @@ -9,6 +9,7 @@ #include "gskvulkanblendpipelineprivate.h" #include "gskvulkanclipprivate.h" #include "gskvulkancolorpipelineprivate.h" +#include "gskvulkanlineargradientpipelineprivate.h" #include "gskvulkanimageprivate.h" #include "gskvulkanpushconstantsprivate.h" #include "gskvulkanrendererprivate.h" @@ -25,6 +26,7 @@ typedef enum { GSK_VULKAN_OP_SURFACE, GSK_VULKAN_OP_TEXTURE, GSK_VULKAN_OP_COLOR, + GSK_VULKAN_OP_LINEAR_GRADIENT, /* GskVulkanOpPushConstants */ GSK_VULKAN_OP_PUSH_VERTEX_CONSTANTS } GskVulkanOpType; @@ -140,6 +142,25 @@ gsk_vulkan_render_pass_add_node (GskVulkanRenderPass *self, g_array_append_val (self->render_ops, op); return; + case GSK_LINEAR_GRADIENT_NODE: + case GSK_REPEATING_LINEAR_GRADIENT_NODE: + if (gsk_linear_gradient_node_get_n_color_stops (node) > GSK_VULKAN_LINEAR_GRADIENT_PIPELINE_MAX_COLOR_STOPS) + FALLBACK ("Linear gradient with %zu color stops, hardcoded limit is %u\n", + gsk_linear_gradient_node_get_n_color_stops (node), + GSK_VULKAN_LINEAR_GRADIENT_PIPELINE_MAX_COLOR_STOPS); + if (gsk_vulkan_clip_contains_rect (&constants->clip, &node->bounds)) + pipeline_type = GSK_VULKAN_PIPELINE_LINEAR_GRADIENT; + else if (constants->clip.type == GSK_VULKAN_CLIP_RECT) + pipeline_type = GSK_VULKAN_PIPELINE_LINEAR_GRADIENT_CLIP; + else if (constants->clip.type == GSK_VULKAN_CLIP_ROUNDED_CIRCULAR) + pipeline_type = GSK_VULKAN_PIPELINE_LINEAR_GRADIENT_CLIP_ROUNDED; + else + FALLBACK ("Linear gradient nodes can't deal with clip type %u\n", constants->clip.type); + op.type = GSK_VULKAN_OP_LINEAR_GRADIENT; + op.render.pipeline = gsk_vulkan_render_get_pipeline (render, pipeline_type); + g_array_append_val (self->render_ops, op); + return; + case GSK_CONTAINER_NODE: { guint i; @@ -350,6 +371,7 @@ gsk_vulkan_render_pass_upload (GskVulkanRenderPass *self, default: g_assert_not_reached (); case GSK_VULKAN_OP_COLOR: + case GSK_VULKAN_OP_LINEAR_GRADIENT: case GSK_VULKAN_OP_PUSH_VERTEX_CONSTANTS: break; } @@ -384,6 +406,11 @@ gsk_vulkan_render_pass_count_vertex_data (GskVulkanRenderPass *self) n_bytes += op->render.vertex_count; break; + case GSK_VULKAN_OP_LINEAR_GRADIENT: + op->render.vertex_count = gsk_vulkan_linear_gradient_pipeline_count_vertex_data (GSK_VULKAN_LINEAR_GRADIENT_PIPELINE (op->render.pipeline)); + n_bytes += op->render.vertex_count; + break; + default: g_assert_not_reached (); case GSK_VULKAN_OP_PUSH_VERTEX_CONSTANTS: @@ -436,6 +463,21 @@ gsk_vulkan_render_pass_collect_vertex_data (GskVulkanRenderPass *self, } break; + case GSK_VULKAN_OP_LINEAR_GRADIENT: + { + op->render.vertex_offset = offset + n_bytes; + gsk_vulkan_linear_gradient_pipeline_collect_vertex_data (GSK_VULKAN_LINEAR_GRADIENT_PIPELINE (op->render.pipeline), + data + n_bytes + offset, + &op->render.node->bounds, + gsk_linear_gradient_node_peek_start (op->render.node), + gsk_linear_gradient_node_peek_end (op->render.node), + gsk_render_node_get_node_type (op->render.node) == GSK_REPEATING_LINEAR_GRADIENT_NODE, + gsk_linear_gradient_node_get_n_color_stops (op->render.node), + gsk_linear_gradient_node_peek_color_stops (op->render.node)); + n_bytes += op->render.vertex_count; + } + break; + default: g_assert_not_reached (); case GSK_VULKAN_OP_PUSH_VERTEX_CONSTANTS: @@ -472,6 +514,7 @@ gsk_vulkan_render_pass_reserve_descriptor_sets (GskVulkanRenderPass *self, default: g_assert_not_reached (); case GSK_VULKAN_OP_COLOR: + case GSK_VULKAN_OP_LINEAR_GRADIENT: case GSK_VULKAN_OP_PUSH_VERTEX_CONSTANTS: break; } @@ -563,6 +606,27 @@ gsk_vulkan_render_pass_draw (GskVulkanRenderPass *self, current_draw_index, step); break; + case GSK_VULKAN_OP_LINEAR_GRADIENT: + if (current_pipeline != op->render.pipeline) + { + current_pipeline = op->render.pipeline; + vkCmdBindPipeline (command_buffer, + VK_PIPELINE_BIND_POINT_GRAPHICS, + gsk_vulkan_pipeline_get_pipeline (current_pipeline)); + vkCmdBindVertexBuffers (command_buffer, + 0, + 1, + (VkBuffer[1]) { + gsk_vulkan_buffer_get_buffer (vertex_buffer) + }, + (VkDeviceSize[1]) { op->render.vertex_offset }); + current_draw_index = 0; + } + current_draw_index += gsk_vulkan_linear_gradient_pipeline_draw (GSK_VULKAN_LINEAR_GRADIENT_PIPELINE (current_pipeline), + command_buffer, + current_draw_index, 1); + break; + case GSK_VULKAN_OP_PUSH_VERTEX_CONSTANTS: gsk_vulkan_push_constants_push_vertex (&op->constants.constants, command_buffer, diff --git a/gsk/gskvulkanrenderprivate.h b/gsk/gskvulkanrenderprivate.h index f707c2a736..72c4314492 100644 --- a/gsk/gskvulkanrenderprivate.h +++ b/gsk/gskvulkanrenderprivate.h @@ -14,6 +14,9 @@ typedef enum { GSK_VULKAN_PIPELINE_COLOR, GSK_VULKAN_PIPELINE_COLOR_CLIP, GSK_VULKAN_PIPELINE_COLOR_CLIP_ROUNDED, + GSK_VULKAN_PIPELINE_LINEAR_GRADIENT, + GSK_VULKAN_PIPELINE_LINEAR_GRADIENT_CLIP, + GSK_VULKAN_PIPELINE_LINEAR_GRADIENT_CLIP_ROUNDED, /* add more */ GSK_VULKAN_N_PIPELINES } GskVulkanPipelineType; diff --git a/gsk/resources/vulkan/linear-clip-rounded.frag.glsl b/gsk/resources/vulkan/linear-clip-rounded.frag.glsl new file mode 100644 index 0000000000..3c5fade7e3 --- /dev/null +++ b/gsk/resources/vulkan/linear-clip-rounded.frag.glsl @@ -0,0 +1,78 @@ +#version 420 core + +struct ColorStop { + float offset; + vec4 color; +}; + +struct RoundedRect { + vec4 bounds; + vec4 corners; +}; + +layout(location = 0) in vec2 inPos; +layout(location = 1) in float inGradientPos; +layout(location = 2) in flat int inRepeating; +layout(location = 3) in flat int inStopCount; +layout(location = 4) in flat vec4 inClipBounds; +layout(location = 5) in flat vec4 inClipWidths; +layout(location = 6) in flat ColorStop inStops[8]; + +layout(location = 0) out vec4 outColor; + +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +float clip(vec2 pos, RoundedRect r) { + vec2 ref_tl = r.bounds.xy + vec2( r.corners.x, r.corners.x); + vec2 ref_tr = r.bounds.zy + vec2(-r.corners.y, r.corners.y); + vec2 ref_br = r.bounds.zw + vec2(-r.corners.z, -r.corners.z); + vec2 ref_bl = r.bounds.xw + vec2( r.corners.w, -r.corners.w); + + float d_tl = distance(pos, ref_tl); + float d_tr = distance(pos, ref_tr); + float d_br = distance(pos, ref_br); + float d_bl = distance(pos, ref_bl); + + float pixels_per_fragment = length(fwidth(pos.xy)); + float nudge = 0.5 * pixels_per_fragment; + vec4 distances = vec4(d_tl, d_tr, d_br, d_bl) - r.corners + nudge; + + bvec4 is_out = bvec4(pos.x < ref_tl.x && pos.y < ref_tl.y, + pos.x > ref_tr.x && pos.y < ref_tr.y, + pos.x > ref_br.x && pos.y > ref_br.y, + pos.x < ref_bl.x && pos.y > ref_bl.y); + + float distance_from_border = dot(vec4(is_out), + max(vec4(0.0, 0.0, 0.0, 0.0), distances)); + + // Move the distance back into pixels. + distance_from_border /= pixels_per_fragment; + // Apply a more gradual fade out to transparent. + //distance_from_border -= 0.5; + + return 1.0 - smoothstep(0.0, 1.0, distance_from_border); +} + +void main() +{ + RoundedRect r = RoundedRect(vec4(inClipBounds.xy, inClipBounds.xy + inClipBounds.zw), inClipWidths); + + float pos; + if (inRepeating != 0) + pos = fract (inGradientPos); + else + pos = clamp (inGradientPos, 0, 1); + + vec4 color = inStops[0].color; + int n = clamp (inStopCount, 2, 8); + for (int i = 1; i < n; i++) + { + if (inStops[i].offset > inStops[i-1].offset) + color = mix (color, inStops[i].color, clamp((pos - inStops[i-1].offset) / (inStops[i].offset - inStops[i-1].offset), 0, 1)); + } + + //outColor = vec4(pos, pos, pos, 1.0); + outColor = color * clip (inPos, r); +} diff --git a/gsk/resources/vulkan/linear-clip-rounded.frag.spv b/gsk/resources/vulkan/linear-clip-rounded.frag.spv new file mode 100644 index 0000000000000000000000000000000000000000..ce8a52a43d6692ebe98fa760ca97e79be8d54d2c GIT binary patch literal 6748 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 inStops[i-1].offset) + color = mix (color, inStops[i].color, clamp((pos - inStops[i-1].offset) / (inStops[i].offset - inStops[i-1].offset), 0, 1)); + } + + //outColor = vec4(pos, pos, pos, 1.0); + outColor = color; +} diff --git a/gsk/resources/vulkan/linear-clip.frag.spv b/gsk/resources/vulkan/linear-clip.frag.spv new file mode 100644 index 0000000000000000000000000000000000000000..a95b58f59bb67df32aa3c53e2b9e48ba39798e4d GIT binary patch literal 2320 zcmYk6+fP$L5XKjJu>yj~%?n}??@B(x=fo%OC#?$!-zmm(jys>T##%?4(bd!DkdwX3dVdDL0ad7LNQob2@1=_ zQoUYm#2>4bVl|8^#d5e^DaGN33VAs%FN{jnowA>hQHI|`#$=K{!f35huWZI)^iH0X z=~mr(tsy&`^cY6L_GUS(MDeP;M*g%pE?ufsYugFVm?7yU@OoUU%g!nbA75>2t5J#f ze9UC;Y&w}i7;%uADRW+NhD3kNrWsqR?L=`BAIE3o%&oQ)C#7YYK+6ZohP-mt!Dy(Z3acRuod%af5Nms#^$pgx${ z@a`R^H{Pm`$1R>yK0WW3(91Wmvd<3dW6!@-k@v;-SNX&aNctu8fejDy@0GCE4zYs! zB+LLFV)NpnSNwl;7G5~uavTG)n;f}>ZtQ2dq!{KS!@PD^Exh9(&{OkdIMFKZm*zW9%Y`B;MzqRWU=767BkjH**NQk*} zv$8q&Nj16qO$k2k<+$c#!$r*JninSa!}hx%8-Dz}p+#vvdM9>Ef{)&@>4Dwcmar4o z(~@j=7j}9_LVf)HPM2-W-R-V0Z_V)pvi-Z=6UOJ??Y=PF#QeKG5GM9ncVli3CGg{S z`~}&rPiFo|0vCRt>#>cwb1TArU#r6S{Jx$D!$r*JS`#Mb`dXI_KmISa*Qc`Sml$ul zOZu6_{icY!-_)x9OR$6IHitYXoF(=`!dc!uyJGI-yWbLV-niQ>8qE7=*JyY5m$JG0 ku6=K>WaH!R?l* z&!T&6D0<*UvA!56+mX?Oqnq2Esm)urU8CdtqPH~axuAGzS@-e(ky@iExdIG<7l7?x zCm08}f0w=&punbOt(_jUh0loa|p?!!&`-*kN$Y^zBY;0t7PjzyB zzS{2GF;}n7HJbJ6RAZq&*=a1*^{vT#jppRs;#6+v$C2;5AOCvRn5lQFGYhq;M!ngo z&bQiEElqFPf!@y^*3lwS_Z1tcThr6+dZ)|1??&@rr?${3`v>99LsRmhvVTug!MU!c zjk(OK(LBJ`?Hed_o0+Q~sxNfvM{j9P)sJ@d8}Xr!*B827P}~EBx|e;ecB9j1HOn=2 zcI9%;o2kba+q1h{&332OWY>=EE7ZM?EX|j5$$hqybB+1x;nrevs{KFRXHe?5H>NtX z>Z{A~o`_PPtv6<7%c)tjuXqXiJZsXFd+;2z7CR#;UiPZqSM1kUTc{mLKFO=f|8vHY?8)-h-Xi{BCwjf?cm?%;yoZ<6n!myEWHZP^KP88fAKxPiSQZ zdg*g)RZn*vqbs!u+D3D%dkT0v^tstavs|T(RBE2@(5_9I=RLG7N%Q=N_QIqsms(nb z!LH%^5x%X-cRJIyC2b|sd>0+-`xIlZqaGoD&#vdydzKF+?(>#=KJE9CJD2aubHQHb zjPs}FHNyE;bNpWXo++<>pg;JgQvH984^r{u%;%o;RWi+a)y6ZeH)#{JuJ0Q9+^@bT z_TO86i_TAdp0)hm4<+t=&g1zCueIKo*E1&fJEGS!5?*Wjy7TlE6N#IFd-9yv@BPX> zBl0zgdp_iA6ZdS${RZh@PJAHoQ;GWw?DstAKQHl>#GjP7=fHmVdl7f!J#A#S?$`QK zD(4;smocCFl-oB9cb{rvBiw!3uYL*Kb%uf0^AdM0Jy*gnpmnU8^ZCr}Q{R#JhNL;( zz6*iZ)tn*6UIf%ccrKyst)^quKT0Ls6{{~v;Qg=RiHED96*#vO@n6F0d<@m$2_PJ){Je{-{Ka;p;q>;R? z8S^ciwFBKXsP>VYuJ9 z==WQcum31Kp11j}oYnt0Jl-k$&!qnC%<6r*7ccMOUnsGb&(OcCVyt|B;y2;;d*VBN zBQyB?uK~VqV~lXjwMkR=a?j!2LY;g4eO43h$-U_Hocg=!d%269`A*&dTw7g_YrP1l zpTN`IZ&5g>`thW_nA$z<0Pf#=Q*&P5tCs@bwF+?FU9=-WJ=WPv?YRAnG4or1npbEq zqgL~N$Jve9yd3DY??P}fxQ{vP8wIYT$Gz;MeFgA)QUUH&t}pg+fZBc7x1UyiL)z0p zYJa!X?f3mxbF6v33iyuO=esYDzT2pMzwPtAm%FFKw3A>0)PR2Pe+sDYg88kf1K;OL zg7c{9bBy;p3uZF@#>B_?YWTjs38)FrK_K#-L(}6G+7W6sGY_qqv>w`c4lQcO>$l(M z;F)n>^T567@qYHvz8S>*OjCP5YI-A=HuVHp0Fg_FT0L@Eq;?+Xr~v0t(-*niPVKiS z_|e28mpkBUA~La!`+W6adgSshYUeoyD!^RS^hFNuraqDJ_aq*<+znR~<`IYt-ixNkE424f ztL3@8KWTX`A3$@we*5!WJ_vVjdLow(QOEs6E^2xsm;0zEz`Y=H`7pJ5Ny~HjB%0&(+n?w1DY$#n z6S;hvI_@WOQPUf_JV?C^Y9Mm?EcJsRa`_y!^Y|ND0p_BnFLHT^`c%e0pLpc*1-P0x zS1w;f*W(qre2H2u&*jTW%X9e(n&b7`pXc&bxO>wRxqOW}?k93l(;K;bo!VUT@5?vf z>XFMgsh#IxPyyzmrY~~&7WL_je>?HW}8ja`_&$TAs@zNy~HjKAPk8 z+n?w11GszB6S@45I_@WOQPUf_{D|6Ij)OQaKc-fXTz*3BJdc73Fc&p_k;`M$D;fW3 z;*rbG;A-Mrx%?bmk5}aK3u?7hnfA-1_0Y!e(XXf-uiySWmtVsl19~Et-%!W>L@sK2 zBbVP&n@j$E`5jz6a``>A^E?hJz+BYyMJ|7!K9li3CLX!`39cs2mCK*e^>{@tf1y^( zbNOr1@?8Fg=6L<~=ehhH?%wo7F8`p8`-xoC^!}UGt_S|TI{;q~yw-y2*!ddTb-=%U z&!hG4Y_&nK8kmReQ-HqUS5jXA?qXj1p9b`(o2Op2@DEcDWqc!bKK|+OVW94K*H?@2 wo2c{kp8?l%GTqlRsr9JGm}gPz3I6QFW6V`>J?eL-d7eY inStops[i-1].offset) + color = mix (color, inStops[i].color, clamp((pos - inStops[i-1].offset) / (inStops[i].offset - inStops[i-1].offset), 0, 1)); + } + + //outColor = vec4(pos, pos, pos, 1.0); + outColor = color; +} diff --git a/gsk/resources/vulkan/linear.frag.spv b/gsk/resources/vulkan/linear.frag.spv new file mode 100644 index 0000000000000000000000000000000000000000..a95b58f59bb67df32aa3c53e2b9e48ba39798e4d GIT binary patch literal 2320 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 Vc^+7e{57f00J*gs(?7BP{SSlO%v1mX literal 0 HcmV?d00001 -- 2.30.2